package com.amithkoujalgi.framework.rest.core;


import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.servlet.http.HttpServlet;

import org.apache.log4j.Logger;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;

import com.amithkoujalgi.framework.rest.util.ConfigValidator;
import com.amithkoujalgi.framework.rest.util.FileUtils;
import com.amithkoujalgi.framework.rest.util.JSONUtil;
import com.amithkoujalgi.framework.rest.util.Templater;

public class ComponentInit
{
	private static Logger logger = Logger.getLogger( ComponentInit.class );
	private int port;
	private Properties props = new Properties();
	private Server server;
	private HandlerCollection handlerCollection;
	private List<Handler> handlers = new ArrayList<Handler>();

	public ComponentInit (int port, Properties props)
	{
		this.port = port;
		this.props = props;
		init();
	}

	public Properties getProperties()
	{
		return props;
	}

	private void init()
	{
		server = new Server( port );
		handlerCollection = new HandlerCollection();

		String logDir = props.getProperty( ConfigKey.LOG_DIRECTORY );
		if( logDir == null || logDir.isEmpty() )
		{
			logDir = AppConfig.DEFAULT_LOG_DIR + File.separator + "http";
			System.err.println( "Config property '" + ConfigKey.LOG_DIRECTORY
					+ "' not found. Using default log directory '" + logDir + "'" );
		}
		else
		{
			logDir = logDir + File.separator + "http";
		}
		File logDirectoryHandle = new File( logDir );
		if( !logDirectoryHandle.exists() )
		{
			logDirectoryHandle.mkdirs();
		}
		NCSARequestLog requestLog = new NCSARequestLog();
		requestLog.setFilename( logDir + File.separator + "yyyy_mm_dd.request.log" );
		requestLog.setFilenameDateFormat( "yyyy_MM_dd" );
		requestLog.setRetainDays( 90 );
		requestLog.setAppend( true );
		requestLog.setExtended( true );
		requestLog.setLogCookies( false );
		requestLog.setLogTimeZone( "GMT" );
		RequestLogHandler requestLogHandler = new RequestLogHandler();
		requestLogHandler.setRequestLog( requestLog );
		handlers.add( requestLogHandler );

		List<String> resourcePkgs = new ArrayList<String>();
		resourcePkgs.add( com.amithkoujalgi.framework.rest.services.CoreSvc.class.getPackage().getName() );
		addSysWebServicePackages( resourcePkgs );

		ResourceHandler resourceHandler1 = new ResourceHandler();
		resourceHandler1.setWelcomeFiles( new String[] { "index.html" } );
		String resourceBase = ConfigValidator.loadDefaultIfConfigPropNotFound( ConfigKey.WEBAPP_RESOURCE_BASE,
				AppConfig.WEBAPP_RESOURCE_BASE );
		File f = new File( resourceBase );
		if( !f.exists() )
		{
			f.mkdirs();
		}
		resourceHandler1.setResourceBase( resourceBase );
		logger.info( "Adding resource base '" + resourceBase + "'" );

		File rb1 = new File( resourceBase );
		if( !rb1.exists() )
		{
			rb1.mkdirs();
		}
		handlers.add( resourceHandler1 );
	}

	@SuppressWarnings("unused")
	private ComponentInit addServlet( HttpServlet servlet, String rootContextPath, String relativeRoute )
	{
		ServletContextHandler contextHandler = new ServletContextHandler( ServletContextHandler.SESSIONS );
		logger.info( "Adding context route: " + rootContextPath + relativeRoute );
		contextHandler.setContextPath( rootContextPath );
		contextHandler.addServlet( new ServletHolder( servlet ), relativeRoute );
		handlers.add( contextHandler );
		return this;
	}

	@SuppressWarnings("unused")
	private ComponentInit addServletsWithSubRoutes( String rootContextPath, Map<String, HttpServlet> servletSubRouteMap )
	{
		ServletContextHandler contextHandler = new ServletContextHandler( ServletContextHandler.SESSIONS );
		contextHandler.setContextPath( rootContextPath );
		for( Entry<String, HttpServlet> e : servletSubRouteMap.entrySet() )
		{
			String route = e.getKey();
			if( !route.startsWith( "/" ) )
			{
				route = "/" + route;
			}
			contextHandler.addServlet( new ServletHolder( e.getValue() ), route );
			logger.info( "Adding context route: " + rootContextPath + route );
		}
		handlers.add( contextHandler );
		return this;
	}

	private ComponentInit addSysWebServicePackages( List<String> servicePkgs )
	{
		ServletHolder sh = new ServletHolder( ServletContainer.class );
		sh.setInitParameter( "javax.ws.rs.Application", MyApp.class.getCanonicalName() );
		for( String pkg : servicePkgs )
		{
			JerseyResourceCfg.getInstance().addPackages( pkg );
		}
		String contextPath = "/rest";
		String relativePath = "/*";
		// logger.info("Adding REST context route: " + contextPath +
		// relativePath);
		ServletContextHandler context = new ServletContextHandler( server, contextPath, ServletContextHandler.SESSIONS );
		context.addServlet( sh, relativePath );
		handlers.add( context );
		return this;
	}

	public ComponentInit addWebServicePackages( List<String> servicePkgs )
	{
		ServletHolder sh = new ServletHolder( ServletContainer.class );
		sh.setInitParameter( "javax.ws.rs.Application", MyApp.class.getCanonicalName() );
		for( String pkg : servicePkgs )
		{
			JerseyResourceCfg.getInstance().addPackages( pkg );
		}
		String contextPath = "/rest";
		String relativePath = "/*";
		logger.info( "Adding REST context route: " + contextPath + relativePath );
		ServletContextHandler context = new ServletContextHandler( server, contextPath, ServletContextHandler.SESSIONS );
		context.addServlet( sh, relativePath );
		handlers.add( context );
		return this;
	}

	public ComponentInit build() throws Exception
	{
		Handler[] hdlrs = handlers.toArray( new Handler[handlers.size()] );
		handlerCollection.setHandlers( hdlrs );
		server.setHandler( handlerCollection );
		server.setStopAtShutdown( true );
		return this;
	}

	private void buildStaticContent()
	{
		String templateBase = "templates/";
		logger.info( "Building static content..." );
		Templater t = new Templater( templateBase + "template-index.html" );
		try
		{
			Map<String, String> substitutions = new HashMap<String, String>();
			substitutions.put( "name", "Amith" );
			FileUtils.writeFile(
					ApplicationContext.getInstance().getConfig().getProperty( ConfigKey.WEBAPP_RESOURCE_BASE )
							+ File.separator + "index.html", t.build( substitutions ) );
		}
		catch( Exception e1 )
		{
			e1.printStackTrace();
		}

	}

	public void startWebServer() throws Exception
	{
		build();
		buildStaticContent();
		logger.info( "Using port " + port + "." );
		logger.info( "Using configuration: \n" + JSONUtil.toJSON( props ) );
		try
		{
			server.start();
		}
		catch( java.net.BindException e )
		{
			logger.error( "Error starting Jetty webserver. Reason: " + e.getMessage() );
			System.exit( 0 );
		}
		catch( java.io.IOException e )
		{
			logger.error( "Error starting Jetty webserver", e );
			System.exit( 0 );
		}
		logger.info( "Server up and running..." );
	}

	public void killApp() throws Exception
	{
		try
		{
			for( Handler h : handlers )
			{
				h.stop();
			}
			server.stop();
		}
		catch( java.lang.InterruptedException e )
		{
			logger.info( "Server interrupted." );
		}
		logger.info( "Server destroyed." );
		System.exit( 0 );
	}
}

class MyApp extends ResourceConfig
{

	public MyApp ()
	{
		init();
		packages( JerseyResourceCfg.getInstance().getPackages() );
	}

	private void init()
	{
		register( MultiPartFeature.class );
	}
}

class JerseyResourceCfg
{
	private static Logger logger = Logger.getLogger( JerseyResourceCfg.class );
	private static JerseyResourceCfg instance;
	private static List<String> packages = new ArrayList<String>();

	public static synchronized JerseyResourceCfg getInstance()
	{
		if( instance == null )
		{
			instance = new JerseyResourceCfg();
			packages = new ArrayList<String>();
		}
		return instance;
	}

	public void addPackages( String packageName )
	{
		logger.info( "Adding resources of package: " + packageName );
		packages.add( packageName );
	}

	public String getPackages()
	{
		String pkgs = "";
		for( String p : packages )
		{
			pkgs = pkgs + "," + p;
		}
		if( pkgs.startsWith( "," ) )
		{
			pkgs = pkgs.substring( 1, pkgs.length() );
		}
		if( pkgs.endsWith( "," ) )
		{
			pkgs = pkgs.substring( 0, pkgs.length() - 1 );
		}
		return pkgs;
	}
}